
#include <process.h>
#include <kmalloc.h>
#include <debug.h>
#include <paging.h>
#include <pmm.h>
#include <gdt.h>

static process_t system_process;
static thread_t system_thread;
static stack_t isr_stack;
stack_t g_system_stack;

static stack_t *system_stack_ptr;
stack_t *g_isr_stack_top;

static word curr_pid;
static word curr_tid;

/**
 * Initialize system processes / stacks and ISR stack
 * 
 * returns - error
 */
int init_processes(void)
{
	int ret = 0;
	
	system_process.threads = &system_thread;
	system_process.pid = 0;
	system_process.state = STATE_RUNNING;
	system_process.next_process = system_process.prev_process = NULL;
	system_process.user = 0;
	
	system_thread.next_thread = system_thread.prev_thread = NULL;
	system_thread.next_sibling = system_thread.prev_sibling = NULL;
	system_thread.process = &system_process;
	system_thread.state = STATE_RUNNING;
	system_thread.tid = 0;
	system_thread.quantum = QUANTUM_STD;
	system_thread.priority = PRIORITY_STD;
	system_thread.stack = &g_system_stack;
    
    system_stack_ptr = &g_system_stack;
    g_isr_stack_top = (void*)&isr_stack + STACKSIZE;  // +STACKSIZE needed...
    
    curr_pid = 1;
    curr_tid = 1;
    
    debugf("[PROC] System processes initialized\n");
    
    return ret;
}

/**
 * Destroy a thread
 * 
 * thread_t *thread - thread to be destroyed
 * 
 * TODO: clean up file handles and whatnot?
 */
void proc_destroy_thread(thread_t *thread)
{
	if (thread == &system_thread)
		return;  // bad news
	
	if (thread->prev_thread != NULL)
		thread->prev_thread->next_thread = thread->next_thread;
	
	if (thread->next_thread != NULL)
		thread->next_thread->prev_thread = thread->prev_thread;
	
	// free thread stack
	kfree(thread->stack);

	// if this is the last thread, kill the process
	if ((thread->next_thread == NULL) && (thread->prev_thread == NULL))
	{
		process_t *proc = thread->process;
		
		if (proc->prev_process != NULL)
			proc->prev_process->next_process = proc->next_process;
		
		if (proc->next_process != NULL)
			proc->next_process->prev_process = proc->prev_process;
		
		debugf("[PROC] Destroyed process #%d\n", proc->pid);
		kfree(proc);
	}
	
	debugf("[PROC] Destroyed thread #%d\n", thread->tid);
	kfree(thread);
}

/**
 * Create a thread associated with a process
 * 
 * process_t *parent - parent process
 * 
 * returns - ptr to newly created thread
 */
thread_t * proc_create_thread(process_t *parent, dword *entry)
{
	thread_t *thread = (thread_t*)kmalloc(sizeof(thread_t));
	if (thread == NULL)
	{
		debugf("[PROC] Could not allocate thread structures\n");
		return NULL;
	}
	
	// create stack
	stack_t *stack = pmm_frame_alloc(1);
	if (stack == NULL)
	{
		debugf("[PROC] Could not allocate stack\n");
	}
	
	// set context
	context_t *context = ((void*)stack + STACKSIZE) - sizeof(context_t);
	
	// convert to virtual address & map that frame into proc's address space
	stack = pg_map_frame(stack, parent->pd);
	//stack = pg_pa2va(stack, parent->pd);
		
	context->ds = context->es = context->fs = context->gs = context->ss = SEL_DATA;  // TODO: change to user data seg
	context->cs = SEL_CODE;
	context->eax = context->ebx = context->ecx = context->edx = 0;
	context->edi = context->esi = 0;
	context->eip = _pa2va((dword)entry, parent->pd);
	context->eflags = DEFAULT_EFLAGS;
	context->ebp = context->esp = 0; // = (void*)stack + STACKSIZE;
	
	thread->context = pg_pa2va(context, parent->pd);
	thread->stack = pg_pa2va(stack, parent->pd);
	
	// TODO: setup bogus return address on stack
	
	thread->process = parent;
	thread->next_sibling = thread->prev_sibling = NULL;
	thread->next_thread = thread->prev_thread = NULL;
	thread->state = STATE_READY;
	thread->tid = proc_get_next_tid();
	thread->quantum = QUANTUM_STD;
	thread->priority = PRIORITY_STD;
	
	return thread;
}

/**
 * Create a process and default thread
 * 
 * dword *entry - entry point
 * 
 * returns - ptr to newly created process, or NULL on error
 */
process_t * proc_create_process(dword *entry)
{
	process_t *proc = (process_t*)kmalloc(sizeof(process_t));
	if (proc == NULL)
	{
		debugf("[PROC] Could not allocate process structures\n");
		return NULL;
	}
	
	proc->pd = system_process.pd; //pg_create_address_space();
	
	proc->threads = proc_create_thread(proc, entry);
	if (proc->threads == NULL)
	{
		debugf("[PROC] Error creating thread\n");
		return NULL;
	}
	
	proc->prev_process = &system_process;
	proc->next_process = system_process.next_process;
	
	if (proc->next_process != NULL)
		proc->next_process->prev_process = proc;
	
	system_process.next_process = proc;
	
	proc->pid = proc_get_next_pid();
	
	proc->user = 0;  // TODO: change this
	
	proc->state = STATE_READY;
	
	//scheduler(proc->threads, proc->threads->priority);
	
	return proc;
}

/**
 * Get the system thread
 * 
 * returns - system thread
 */
thread_t * proc_get_systhread(void)
{
	return &system_thread;
}

/**
 * Get the system process
 * 
 * returns - system process
 */
process_t * proc_get_sysprocess(void)
{
	return &system_process;
}

/**
 * Gets a unique process id
 * 
 * returns - process id
 */
byte proc_get_next_pid(void)
{
	return curr_pid++;
}

/**
 * Gets a unique thread id
 * 
 * returns - thread id
 */
byte proc_get_next_tid(void)
{
	return curr_tid++;
}
